﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XMLGener.Data;

namespace XMLGener.Utils
{
    /// <summary>
    /// 
    /// </summary>
    public class XSDConverterNode
    {
        private static int TAB_SPACES = 3;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="parent"></param>
        public XSDConverterNode(XMLGenerNode node,XMLGenerXSDConverter parent)
        {
            this.parent = parent;
            this.elemNode = node;
            this.elem = elemNode as XMLGenerElement;
            this.elems = elemNode as XMLGenerElements;


            if (this.elems != null)
                this.isGroup = true;
            if (this.elem != null)
                this.isElement = true;
        }
        private XMLGenerXSDConverter parent = null;

        /// <summary>
        /// 
        /// </summary>
        public string groupId = "";
        private XMLGenerNode elemNode = null;
        private XMLGenerElement elem = null;
        private XMLGenerElements elems = null;
        private List<XSDConverterNode> childs = null;

        /// <summary>
        /// 
        /// </summary>
        public bool isElement = false;
        /// <summary>
        /// 
        /// </summary>
        public bool isMixed = false;
        /// <summary>
        /// 
        /// </summary>
        /// <summary>
        /// 
        /// </summary>
        public bool isString = false;
        /// <summary>
        /// 
        /// </summary>
        public bool isGroup = false;
        /// <summary>
        /// 
        /// </summary>
        public bool isMainElement = false;

        /// <summary>
        /// 
        /// </summary>
        public List<XSDConverterNode> getChilds()
        {
            if (childs == null)
                initializeChilds();
            return childs;
        }

        /// <summary>
        /// 
        /// </summary>
        public bool childsInStack = false;
        /// <summary>
        /// 
        /// </summary>
        public bool isInitialized = false;

        private void initializeChilds()
        {
            if (childs != null)
                return;
            childs = new List<XSDConverterNode>();
            foreach (XMLGenerNode ch in elemNode.childNodes)
            {
                if (ch is XMLGenerElement || ch is XMLGenerElements)
                {
                    childs.Add(new XSDConverterNode(ch,parent));
                }
                else if (ch is XMLGenerTextNode)
                {
                    isMixed = true;
                }
            }
            if (isMixed && this.childs.Count == 0)
                isString = true;
            if (isString && isElement && (elem.attributes.Count > 0))
                isString = false;
        }

        /// <summary>
        /// 
        /// </summary>
        public void init()
        {
            if (isInitialized)
                return;
            isInitialized = true;
            initializeChilds();

            bool everyChildElemIsString = childs.Count > 0;
            if (!isMixed || !isString)
            {
                foreach (XSDConverterNode chNode in this.childs)
                {
                    everyChildElemIsString = everyChildElemIsString && chNode.isGroup && chNode.isString;
                    if (chNode.isMixed && chNode.isGroup)
                    {
                        this.isMixed = true;
                    }
                }
                this.isString = this.isString || everyChildElemIsString;
                if (isElement)
                {
                    this.isString = this.isString && (this.elem.allChildNodes.Count == 0);
                }


            }
        }

        private static StringBuilder addSpaces(StringBuilder sb, int depth)
        {
            return sb.Append(' ', TAB_SPACES * depth);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sb"></param>
        public void print(StringBuilder sb)
        {
            if (this.isString)
                return;
            
           
            if (this.isElement)
            {
                if (this.isMainElement)
                {
                    
                    addSpaces(sb, 1).Append("<xs:element name=\"").Append(getTypeName()).Append("\" ");
                    if (isMixed)
                        sb.Append(" mixed=\"true\"");
                    sb.Append(" >\n");
                    addSpaces(sb, 2).Append("<xs:complexType>\n");
                    addElementContent(sb ,3);
                    addSpaces(sb, 2).Append("</xs:complexType>\n");
                    addSpaces(sb, 1).Append("</xs:element>\n");
                }
                else
                {

                    addSpaces(sb, 1).Append("<xs:complexType name=\"").Append(getTypeName()).Append("\"");
                    if (isMixed)
                        sb.Append(" mixed=\"true\"");
                    sb.Append(" >\n");
                    addElementContent(sb, 2);
                    addSpaces(sb, 1).Append("</xs:complexType>\n");
                }

            }
            else if (isGroup)
            {
                addGroupDefinition(sb,1);
            }



        }
        private void addElementContent(StringBuilder sb, int depth)
        {
            addSpaces(sb, depth).Append("<xs:sequence>\n");
            int nextDepth = depth+1;
            foreach (XSDConverterNode node in this.childs)
            {
                node.getRefDefinition(sb, nextDepth, false);
            }
            addSpaces(sb, depth).Append("</xs:sequence>\n");
            foreach (XMLGenerAttribute xattr in elem.attributes)
            {
                if (xattr == null) 
                    continue;
                addSpaces(sb,depth).Append("<xs:attribute name=\"");
                sb.Append(xattr.name).Append("\" type=\"xs:string\"  />\n");
            }

            
        }


        private void addGroupDefinition(StringBuilder sb,int depth)
        {
            addSpaces(sb, depth).Append("<xs:group name=\"").Append(this.getTypeName()).Append("\">\n");
            addSpaces(sb, depth+1).Append("<xs:sequence>\n");
            int nextDepth = depth + 2;
            bool isRandomOrder = (elems.contentType=="random");
            foreach (XSDConverterNode node in this.childs)
            {
                node.getRefDefinition(sb, nextDepth, isRandomOrder);
            }
            addSpaces(sb, depth+1).Append("</xs:sequence>\n");
            addSpaces(sb, depth).Append("</xs:group>\n");
        }

        private static decimal unboundedCount = 1000000000000;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sb"></param>
        /// <param name="depth"></param>
        /// <param name="randomOrder"></param>
        public void getRefDefinition(StringBuilder sb,int depth,bool randomOrder)
        {  
            if (isElement)
            {
                addSpaces(sb,depth);
                sb.Append("<xs:element name=\"").Append(elem.name).Append("\" ");
                if (elem.useFrequency )
                {
                    sb.Append(" minOccurs=\"0\"  maxOccurs=\"unbounded\" ");
                }else
                {
                    if (randomOrder)
                    {
                        sb.Append(" minOccurs=\"0\"  maxOccurs=\"");
                        if (elem.maxCount > unboundedCount)// just cosmetic
                            sb.Append("unbounded\" ");
                        else
                            sb.Append(elem.maxCount).Append("\" ");
                    }
                    else 
                    {
                        sb.Append(" minOccurs=\"").Append(elem.minCount).Append("\"  maxOccurs=\"");
                        if (elem.maxCount > unboundedCount)// just cosmetic
                            sb.Append("unbounded\" ");
                        else
                            sb.Append(elem.maxCount).Append("\" ");
                    }
                }
                sb.Append(" type=\"");
                sb.Append(this.getTypeName());
                sb.Append("\" />\n");
            }
            else if (isGroup)
            {
                addSpaces(sb, depth).Append("<xs:group ref=\"").Append(getTypeName()).Append("\"");
                if (elems.useFrequency)
                {
                    sb.Append(" minOccurs=\"0\"  maxOccurs=\"unbounded\"");
                }
                else
                {
                    if (randomOrder|| elems.contentType=="random")
                    {
                        sb.Append(" minOccurs=\"0\"");
                        sb.Append(" maxOccurs=\"unbounded\"");
                    }
                    else
                    {
                        sb.Append(" minOccurs=\"").Append(elems.minCount).Append("\"  maxOccurs=\"");
                        if (elems.maxCount > unboundedCount)// just cosmetic
                            sb.Append("unbounded\" ");
                        else
                            sb.Append(elems.maxCount).Append("\" ");
                    }
                }
                sb.Append("/>\n");
            
            }

        
        }

        private string _typeName = null;
        private string getTypeName()
        {
            if (this.isString)
            {
                this._typeName = "xs:string";
            }
            if (_typeName != null)
                return _typeName;
            if (isMainElement)
            {
                _typeName = elem.name;
                return _typeName;
            }

            StringBuilder sb = new StringBuilder();
            if (isGroup)
            {
                int i = 1;
                bool unique = false;
                while (!unique)
                {
                    sb.Length = 0;
                    sb.Append("group").Append(i);
                    if (!parent.groupsDict.ContainsKey(sb.ToString()))
                        unique = true;
                    i++;
                }
                _typeName = sb.ToString();
                parent.groupsDict.Add(_typeName, this);

            }
            else if (isElement)
            {
                int i = 0;
                bool unique = false;
                while (!unique)
                {
                    sb.Length = 0;
                    sb.Append(elem.name);
                    if (i > 0)
                        sb.Append(i);
                    sb.Append("Type");
                    if (!parent.groupsDict.ContainsKey(sb.ToString()))
                        unique = true;
                    i++;
                }
                _typeName = sb.ToString();
                parent.groupsDict.Add(_typeName, this);
            }
            else
                _typeName = "";


            return _typeName;
        }



    }
}
